<?php
/**
 * 这里提供了资源统一调用，及一些基础框架功能，所有的请求都是以n_为前缀
 * 
 * 静态资源统一调用:
 * 		提供了插件的css,js,img静态文件的调用。
 * 插件交互:
 * 		提供了插件将的相互调用。
 * 系统配置:
 * 		系统提供了简单的基于文件的配置数据管理
 * 系统日志:
 * 		系统提供了简单的基于文件的日志数据保存
 * 
 */

if (!function_exists('get_weixin_face')) {
	function get_weixin_face($name, $type="jpg")
	{
		if ($type == 'png')
			$path = 'http://img.topranking.cn/static/weixin_face/' . $name . '.png';
		else
			$path = 'http://img.topranking.cn/static/weixin_face/' . $name . '.jpg';
		return $path;
	}
}

if (!function_exists('get_weixin_thumbnail')) {
	function get_weixin_thumbnail($name)
	{
		$path = 'http://img.topranking.cn/static/weixin_thumbnail/' . $name . '.jpg';
		return $path;
	}
}

if(!function_exists('utf8_str')){
     function utf8_str($str){
        $en = array('ASCII','EUC-CN','GB2312','GBK', 'CP936');
         $encoding = mb_detect_encoding($str, $en);
     if(in_array($encoding, $en)) {
    $str = iconv("GBK", "UTF-8//IGNORE", $str);
  }
   return $str;
  }
}

/**
 * 返回ipv4的ipv6格式
 * 
 * @example 	n_ipv4To6('192.168.1.1');
 * 
 * @param 		string 		ipv4地址
 * 
 * @return 		string 		ipv6地址
 * 
 */
function myDechex($num)
{
	$rs = dechex($num);
	if(strlen($rs) == 1)
		return '0'.$rs;
	else
		return $rs;
}

function n_ipv4To6($str)
{
	if ( filter_var( $str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) )
	{
		$ipArr = explode('.', $str);

		$ipv6 = '::' . myDechex($ipArr[0]) . myDechex($ipArr[1]) . ':' . myDechex($ipArr[2]) . myDechex($ipArr[3]);

		return trim($ipv6);
	}
	else if ( filter_var( $str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) )
	{
		return trim($str);
	}
	else
	{
		return n_state(false, "参数不是合法的ip地址");
	}
}

/**
 * 返回ipv6的ipv4格式
 * 
 * 只转可转的ipv6地址，否则直接返回
 *
 * @example 	n_ipv4To6('::c0a8:6ac9');
 * 
 * @param 		string 		ipv6地址
 * 
 * @return 		string 		ipv4地址
 * 
 */
function n_ipv6To4($str)
{
	if ( filter_var( $str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) )
	{
		return trim($str);
		// return hexdec(substr($ipArr[2], 0, 2)) . '.' . hexdec(substr($ipArr[2], 2, 4)) . '.' . hexdec(substr($ipArr[3], 0, 2)) . '.' . hexdec(substr($ipArr[3], 2, 4));
	}
	else if ( filter_var( $str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) )
	{
		if(strlen($str) == 11 && $str[0]==':' && $str[1]==':' && $str[6]==':')
		{
			return hexdec(substr($str, 2, 2)) . '.' . hexdec(substr($str, 4, 2)) . '.' . hexdec(substr($str, 7, 2)) . '.' . hexdec(substr($str, 9, 2));
		}
		else
		{
			return trim($str);
		}
	}
	else
	{
		return n_state(false, "参数不是合法的ip地址");
	}
}

/**
 * 返回一个插件下的css
 * 
 * 根据参数数量不同会有不同的行为。
 * 如果参数数量为2个，那么第一个参数为event,第二个参数为action
 * 如果参数数量为1个，那么默认采用N_EVENT，唯一参数为action
 * 
 * @example 	n_css('pevent','cssFilename');
 * 
 * @param 		string 		插件码
 * @param 		string 		css文件的文件名，后缀不带.css
 * 
 * @return 		string 		css文件路径
 * 
 */
function n_css(){
	$paramNum = func_num_args();
	
	if(2==$paramNum){
		$name = func_get_arg(1);
		$plugin = func_get_arg(0);
	}else{
		$name = func_get_arg(0);
		$plugin = N_EVENT;
	}
	
	return "/".$plugin.'/css/'.$name.'.css';
}



/**
 * 返回一个插件下的js
 * 根据参数数量不同会有不同的行为。
 * 如果参数数量为2个，那么第一个参数为event,第二个参数为action
 * 如果参数数量为1个，那么默认采用N_EVENT，唯一参数为action
 * 
 * @example 	n_js('pevent','jsFilename');
 * 
 * @param 		string 		插件码
 * @param 		string 		js文件的文件名，后缀不带.js
 * 
 * @return 		string 		js访问路径
 * 
 */
function n_js(){
	$paramNum = func_num_args();
	
	if(2==$paramNum){
		$name = func_get_arg(1);
		$plugin = func_get_arg(0);
	}else{
		$name = func_get_arg(0);
		$plugin = N_EVENT;
	}

	return "/".$plugin.'/js/'.$name.'.js';
}





/**
 * 返回图片
 * 
 * 根据参数数量不同会有不同的行为。
 * 如果参数数量为2个，那么第一个参数为event,第二个参数为action
 * 如果参数数量为1个，那么默认采用N_EVENT，唯一参数为action
 * 
 * @example 	n_img('pevent','imgFilename.png');
 * 
 * @param 		string 		插件码
 * @param 		string 		图片文件的文件名，由于图片文件类型多样，所以文件的后缀不能忽略
 * 
 * @return 		string 		图片文件路径
 */
function n_img(){
	$paramNum = func_num_args();
	
	if(2==$paramNum){
		$file = func_get_arg(1);
		$plugin = func_get_arg(0);
	}else{
		$file = func_get_arg(0);
		$plugin = N_EVENT;
	}
	return "/".$plugin.'/img/'.$file;
}


/**
 * 判断是否是wap终端
 * 
 * 该方法从common插件中提取出来，因为有很多底层需要来判断。
 * wap成为一个访问方式之一
 * 
 * @example 	n_iswap();
 * 
 * @return 		bool 		true:为手机端浏览器，false:非手机端浏览器
 * 
 */
function n_isWap(){
	if(!empty($_SERVER['DOCUMENT_ROOT']) && stristr($_SERVER['HTTP_USER_AGENT'],'Mobile')){
		return true;
	}
	return false;
}

/**
 * 构造一个url,为了构造统一的URL，请不要直接在代码中写url，而是通过该函数来实现
 * 插件中的所有url请使用该函数来生成。能很好的提供一致的URL，避免在配置及插件分享时出现不一致的url错误
 * 
 * @example 	n_url('pevent','paction',array('id'=>1));
 * 
 * @param 		string 		插件码
 * @param 		string 		访问方法
 * 
 * @return 		string 		完整的url地址
 *  
 */
function n_url(){
	
	$paramNum = func_num_args();
	
	if(3==$paramNum){
		$event = func_get_arg(0);
		$action = func_get_arg(1);
		$param = func_get_arg(2);
	}elseif(2==$paramNum){
		$event = func_get_arg(0);
		$action = func_get_arg(1);
		$param = array();		
	}else{
		$event = N_EVENT;
		$action = func_get_arg(0);
		$param = array();
	}
	
		
	$paramstr = '';

	if(!empty($param) && is_array($param)){
		foreach($param as $key=>$val){
			$paramstr .= "&{$key}=".urlencode($val);
		}
	}
	

	$event = empty($event)?N_EVENT:$event;
	$action = empty($action)?N_ACTION:$action;
	//linruiyan@2013年2月19日 增加是否wap的判断。如果是wap那么自动添加wap
	//该方法导致wap是一个重要的访问方式。并且和电脑访问完全独立开来。如果想让wap访问一个非wap,那么需要自己手动再封装
	if(n_isWap()){
		$action = 'wap'.$action;
	}
	if('Y'==N_BURL){
		$rs = "/{$event}/{$action}.html";
		if(!empty($paramstr)){
			$rs .= '?'.substr($paramstr, 1);
		}
	}else{
		$rs = "?event={$event}&action={$action}{$paramstr}";
	}
	
	return $rs;
}


/**
 * 跳转到另外一个url，一个很好用的简单函数。
 * 
 * @param 	$url 	string 		跳转的URL
 * 
 */
function n_go($url){
	if(N_SCRIPT=='cli'){
		echo '命令模式不能跳转:'.urldecode($url);
		exit;
	}
	header("location: {$url}");
	exit;	
}


/**
 * 构造一个弹窗的超链接
 * 
 * @param 	$url 		string 	构建的链接
 * @param 	$txt 		string 	链接中的文字
 * @param 	$tagAttr 	string 	给标签加上一个属性，可以控制链接的样式
 * 
 * @return 	$html 		string 	生成对应的链接的html
 */
function n_ui($url,$txt,$tagAttr='class="n-href"'){
	return '<a '.$tagAttr.' href="javascript:void(0);" onclick="ui.open(\''.$url.'\')"><span>'.$txt.'</span></a>';
}


/**
 * 构造一个超链接
 * 
 * @param 	$url 		string 	构建的链接
 * @param 	$txt 		string 	链接中的文字
 * @param 	$tagAttr 	string 	给标签加上一个属性，可以控制链接的样式
 * 
 * @return 	$html 		string 	生成对应的链接的html
 */
function n_href($url,$txt,$tagAttr='class="n-href"'){
	return '<a '.$tagAttr.' href="'.$url.'"><span>'.$txt.'</span></a>';
}



/**
 * 插件模块加载,存在某一些情况需要返回一个能接受任何调用，但又无任何响应。出现这种情况很少。
 * 比如存在某些情况调用mod，在ajax情况下不需要返回。这时就可以return new Nnone()
 * 该功能以类的形态出现
 * 
 * @example $none = new Nnone();
 * 
 *
 */
class Nnone{
	public function __get($key){return new Nnone();}
	public function __call($methon,$param){return new Nnone();}
}




/**
 * 这是一个非常重要的插件调度函数，插件将的api共享就是通过这个函数来实现的。
 *
 * @log
 * 2013年11月15日 增加了忽略调用失败。有时就是尝试性的调用下接口。
 * 
 * @example n_api('pevent')
 * 
 * @param 	$pName 		string 	插件码
 * 
 * @return 	$handle 	obj 	返回插件的api对象
 * 
 * 
 */
function n_api($pName,$isIgnore='N'){

	$pName = strtolower($pName);
	$className = ucfirst($pName).'Api';
	try{
		if(!class_exists($className,false)){
			$path = N_PLUGINS . '/' . $pName . '/' . $className . '.php';
			
			if(!file_exists($path)){
				throw new CException("API:找不到插件{$pName}");
			}
	
			require_once $path;
		}
		return new $className(); 
		
	}catch(CException $c){
		if('Y'==$isIgnore){
			return new Nnone();
		}else{
			throw new CException($c->getMessage());
		}
	}
	
}





/**
 * 和n_api一样的机制，这里提供了嵌入某个插件的模块到另外一个插件的界面中
 * 
 * @example 	$handle = n_mod('pevent');
 * 				$handle->action($param1,$param2);
 * 
 * @param 	$pName string 	插件码
 * @param 	$pajax bool 	是否是ajax请求，如果是将过滤掉返回
 * @return 	obj 			插件对应的mod对象
 * 
 */
function n_mod($pName,$pajax=true){
	if(in_array(N_TYPE,array('ajax','iframe')) && (true == $pajax)){
		//??linruiyan@2013年2月19日 没想起来为什么要返回这个，
		//linruiyan@2013年2月19日 如果是ajax请求方式，直接执行会导致debug跟着一起输出。为了避免在ajax中显示debug，屏蔽掉所有
		//linruiyan@2013年6月11日 图片拖拽上传，需要加载js等复杂操作，去掉了该屏蔽
		//return new Nnone(); 
	}
	$pName = strtolower($pName);
	$className = ucfirst($pName).'Mod';
	if(!class_exists($className,false)){
		$path = N_PLUGINS . '/' . $pName . '/' . $className . '.php';
		
		if(!file_exists($path)){
			throw new CException('Module:找不到插件');
		}

		require_once $path;
	}
	return new $className(); 
}

/**
 * 和n_api一样的机制，这里提供了插件的webservice接口
 * 这个函数很少被用到。仅在open插件中出现
 * 
 * @example 	$handle = n_open('pevent');
 * 				$handle->action($param1,$param2);
 * 
 * @param 	$pName string 	插件码
 * @return 	obj 			插件对应的mod对象
 * 
 */
function n_open($pName){

	$pName = strtolower($pName);
	$className = ucfirst($pName).'Open';
	if(!class_exists($className,false)){
		$path = N_PLUGINS . '/' . $pName . '/' . $className . '.php';
		
		if(!file_exists($path)){
			throw new CException("Module:找不到插件".$path);
		}

		require_once $path;
	}
	return new $className(); 
}

function n_rpc($pName) {
	$pName = strtolower($pName);
	$className = ucfirst($pName) . 'Rpc';

	if(!class_exists($className,false)){
		$path = N_PLUGINS . '/' . $pName . '/' . $className . '.php';
		
		if(!file_exists($path)){
			throw new CException("Module:找不到插件".$path);
		}

		require_once $path;
	}
	return new $className(); 
}



/**
 * 自动加载类，只能是自己lib目录。
 * 这个是PHP提供的魔术函数，不可调用
 * 
 * linruiyan@2013年6月7日 再注册多个自动加载函数时（加载第三方库），需要做放弃处理。比如phpexcel
 * 
 */
spl_autoload_register(function ($className) {
            $trace = debug_backtrace();
            if (isset($trace[1]) && !empty($trace[1]['file'])) {
                preg_match("/.*\\/(\\w*)\\/.*\\.php$/", $trace[1]['file'], $rs);

                if (isset($rs['1'])) {

                    $path = N_PLUGINS . '/' . $rs['1'] . '/lib/' . $className . '.php';

                    $autoCount = count(spl_autoload_functions());
                    if (file_exists($path)) {
                        require_once $path;
                    } elseif (!file_exists($path) && 1 == $autoCount) {
                        throw new CException("无法加载文件:" . $path);
                    }
                }
            }
        });





/**
 * 读取一个系统级配置
 * 
 * @example $conf = n_confGet('pevent');
 * 
 * @param 	$filename string 	配置文件以插件的名字命名
 * @return 	n_state 			返回一个n_state数组
 */
function n_confGet($filename){
	$filepath = N_CONF . "/{$filename}";

	if(!file_exists($filepath)){
		return n_state(false,"找不到配置文件");
	}
	$rs = n_state(true,"读取成功");
	$rs['data'] = unserialize(file_get_contents($filepath));
	return $rs;
}



/**
 * 配置文件的保存，所有的数据都是以序列化的方式存入文件的。
 * 
 * @++2012-05-20 插件只能读取，保存以自己名字开头的配置文件。如果需要其他插件的配置信息，那么自行向插件API请求，怎么什么结果插件自行控制
 * 
 * @example n_confSave('pevent',$newConf);
 * 
 * @param 	$filename 	string 	配置文件的名字
 * @param 	$val 		mix 	保存的数据，可以是任意数据类型
 * 
 * @return 	n_state 			返回一个nstate数组
 */
function n_confSave($filename,$val){
	$filepath = N_CONF . "/{$filename}";	
	file_put_contents($filepath,serialize($val));
	return n_state(true,"保存成功");
}









/**
 * 很多地方需要返回一个内部的响应状态，可能是允许的业务错误，同时需要返回响应说明。那么这个函数提供了一个非常方便的方式
 * 
 * @example 	n_state(FALSE,'错误了哦');
 * 
 * @param 	$type 	bool 	状态类型
 * @param 	$msg 	string 	相应信息
 * 
 * @return 	array 			他其实也是个数组
 * 
 */
function n_state($type,$msg){
	
	if(!is_string($msg)){
		$msg = "msg参数必须为字符串";
		$type = false;
	}
	
	if(!in_array($type,array(true,false))){
		$type = false;
		$msg .= ";type参数必须为true,false";
	}
	return array("state"=>$type,"msg"=>$msg);
}



/**
 * 框架级的日志功能。为出现严重问题时提供记录。管理员需要定时查看这个目录的记录
 * 
 * @example n_logSave('access','请求:pevent/paction,响应时间:100毫秒,内存占用:100MB');
 * 
 * @param 	$file 	string 	保存的文件名
 * @param 	$data 	string 	需要保存的数据
 * @param 	$type 	string 	已追加的方式保存
 * 
 * @return  null
 */
function n_logSave($file,$data,$type='append'){
	
	if(!is_string($data)){
		$data = "data参数必须为字符串";
	}
	
	$data .= "\n"; 
	
	if('append'==$type){
		file_put_contents(N_LOG . "/{$file}",$data,FILE_APPEND);
	}else{
		file_put_contents(N_LOG . "/{$file}",$data);
	}
	
}

/**
 * 框架级别的debug功能，将结果直接保存到log中
 * 
 * @example   n_debug()
 * 
 * @return null
 */
function n_debug($pre=''){
	if(!defined('N_DEBUG') || '0'==N_DEBUG)return false;
	$str = N_EVENT.'/'.N_ACTION."\t";
	$str.= '时间:'.date("Y-m-d H:i:s")."\t";			//请求时间
	$str.= '内存:'.memory_get_usage(true)."\t";		//本次请求消耗的内存
	$str.= '文件:'.count(get_included_files())."\t";	//本次请求包含的文件数
	$str.= '耗时:'.round((microtime(TRUE) - N_MTIME)*1000,2);	//消耗的时间
	n_logSave("debug_".$pre.date("Y-m-d"), $str);
}


/**
 * 在出现比较底层的异常时，样式插件未能完成加载，异常将抛出不完整的html
 * 这里将把这些不完整的html给与比较友好的展示出来
 * 
 * @example 	n_nopage();
 * 
 */
function n_nopage(){
	if(in_array(N_TYPE,array('ajax','iframe'))){
	
		// ob_start();
		$msg = ob_get_contents();
		ob_end_clean();
					
		if('ajax'==N_TYPE){
			$rs = ($msg);
		}elseif('iframe'==N_TYPE){
			$rs = "<html><head></head><body><script>";
			$rs .= "parent.ui.data=".json_encode(array('state'=>true,'method'=>N_METHOD,'msg'=>$msg)).";";
			$rs .= "parent.ui.fresh();";
			$rs .= "</script></body></html>";
		}		
		return $rs;//echo $rs;
	}
}


/**
 * 提供一个调试函数，通过这个函数能打印出好看的debug。如果部署到线上，这些输出将自动输出到log文件中
 * linriuyan@2012-7-19 为了方便调试，提供一个短的把变量打印出来的函数
 * history:sunjian@2012-11-20 $type<>0时格式化输出数据
 * 
 * @example 	n_p($obj);
 * 
 * @param 	$data 	mix 	任意数据
 * @param 	$type 	bool 	输出类型
 * 
 * @return 	null
 */
function n_p($data,$type='0'){
	if ($type != 0) {
		echo '<pre>';
	}
	print_r($data);
	if ($type != 0) {
		echo '</pre>';
	}
}


/**
 * linruiyan@2012-7-20 唯一码是插件间调用的好机制。提到了框架级别来处理
 * 
 * @example 	n_isUcode($str);
 * 
 * @param 	$str 	string 	待检查的字符串
 * 
 * @return 	bool
 */
function n_isUcode($str){
	if(preg_match("/^[a-z][a-z_0-9]{4,30}$/", $str)){
		return true;
	}
	return false;
}



/**
 * linruiyan@2012-7-20 判断是否是规范的插件命名。插件名字必须以字母开头,后面跟着4-20位的字母+数字
 * 
 * @example 	n_isEvent($str);
 * 
 * @param 	$str 	string 	待检查的字符串
 * 
 * @return 	bool
 * 
 */
function n_isEvent($str){
	if(preg_match("/^[a-z][a-z0-9]{4,20}$/", $str)){
		return true;
	}
	return false;
}



/**
 * linruiyan@2012-7-20 判断是否是规范的插件方法。方法名字必须以字母开头,后面跟着2-40位的字母+数字
 * 
 * @example 	n_isAction($str);
 * 
 * @param 	$str 	string 	待检查的字符串
 * 
 * @return 	bool
 */
function n_isAction($str){
	if(preg_match("/^[a-z][a-z0-9]{2,20}$/", $str)){
		return true;
	}
	return false;
}



/**
 * 严重错误，后续会加入更多的处理业务，比如增加日志
 * 
 * @example 	n_fault($except);
 * 
 * @param 	$c 	CException 	一个内核异常
 * 
 * @return 	null
 */
function n_fault($c){
	if(''==N_500){
		//print_r($c);
		//echo $c->getMessage();
		echo '<div class="content"><div class="n-notice">'.$c->getMessage().'</div></div>';
		
	}else{
		$ea = explode("/", N_500);
		n_go(n_url($ea[0],$ea[1]));
	}
}


/**
 * 警告错误，后续会加入更多的处理业务，比如相对友好的界面
 * 
 * @example 	n_alert($except);
 * 
 * @param 	$c 	PException 	一个插件异常
 * 
 * @return 	null
 * 
 */
function n_alert($p){
	if(''==N_500){
		//print_r($p);
		echo '<div class="content"><div class="n-notice">'.$p->getMessage().'</div></div>';
		//echo $p->getMessage();
	}else{
		$ea = explode("/", N_500);
		n_go(n_url($ea[0],$ea[1]));
	}
}


/**
 * 未明确的错误，框架定义了内核异常和插件异常，如果非这两种异常，那么将捕获到原始异常
 * 
 * @example 	n_ufo($except);
 * 
 * @param 	$c 	exception 	一个其他异常
 * 
 * @return 	null
 */
function n_ufo($e){
	if(''==N_404){
		//print_r($e);
		echo '<div class="content"><div class="n-notice">'.$e->getMessage().'</div></div>';
	}else{
		$ea = explode("/", N_404);
		n_go(n_url($ea[0],$ea[1]));
	}
}

/**
 * 判断变量是否为空，如果空则抛出异常
 * 
 * @example 	$data = n_empty(n_api("demo")->get(23),"错误请求");
 * 
 * @param 	$val 	mix 	任何数据
 * 
 * @return 	$val
 */

function n_empty($val,$msg){
	if(empty($val)){
		throw new PException($msg);
	}
	return $val;
}

/**
 * daemon
 *
 * 加载daemon类
 * 
 * @param  string $pName 插件名
 * @param  string $daemon daemon类名字
 * @return void
 */
function daemon($pName, $daemon) {

	if(empty($pName) || empty($daemon)) return false;

	$className = ucfirst(strtolower($daemon));
	$arys[] = N_PLUGINS;
	$arys[] = strtolower($pName);
	$arys[] = 'daemon';
	$arys[] = $className . '.php';
	$file = implode('/', $arys);

	try {
		if(!file_exists($file)) {
			throw new Exception("Module:找不到daemon插件" . $file);
		}
		require_once($file);

		new $className();
	} catch(Exception $e) {
		exit($e->getMessage());
	}
	
}

/**
 * n_buildSelect 构建下拉列表
 * 
 * @param  array $data     
 * @param  string $key 被选中的项
 * @return string           
 */
function n_buildSelect($data, $key = null) {
	if(!is_array($data) || empty($data)) return '';

	foreach ($data as $k => $value) {

		$selected = ($k == $key && isset($key)) ? ' selected' : '';
		$ary[] = "<option value='{$k}' {$selected}>{$value}</option>";
	}

	return implode('', $ary);
}
